package hu.bme.tmit.futureinternet.simulator;

import hu.bme.tmit.futureinternet.log.Logger;
import hu.bme.tmit.futureinternet.model.User;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Scanner;

import org.jgrapht.graph.DefaultWeightedEdge;
import org.jgrapht.graph.SimpleWeightedGraph;

public class TrendSpreadingSimulator {

	private final int INFINITE = Integer.MAX_VALUE;
	private String suffix = Main.SUFFIX;
	private List<String> simulationCase;
	Logger log = new Logger(TrendSpreadingSimulator.class);

	public TrendSpreadingSimulator(String file) {

		long start = System.currentTimeMillis();

		try {

			simulationCase = fileReader(file + suffix);

		} catch (Exception e) {
			System.out.println(e.toString());
		}
		if (simulationCase == null || simulationCase.isEmpty()) {
			simulationCase = generateSimulation(file + suffix);
			log.i(MessageFormat
					.format("{0} simulation of {1} sim.cases generated over population {2} (~ {3} msec).",
							Integer.toString(Main.SIZE_OF_SIMULATIONS),
							Integer.toString(Main.SIZE_OF_SIMULATION_CASES),
							Integer.toString(Main.SIZE_OF_COMMUNITY),
							Long.toString(System.currentTimeMillis() - start)));
		} else {
			log.i(MessageFormat.format(
					"All simulationCases read in (~ {0} msec).",
					System.currentTimeMillis() - start));
		}

	}

	public void Simulate(SimpleWeightedGraph<User, DefaultWeightedEdge> graph)
			throws IOException {

		log.i("Simulate()");

		int SoS = Main.SIZE_OF_SIMULATIONS;

		for (String actualSimulation : simulationCase) {

			long start = System.currentTimeMillis();

			// SimulateSimCase(graph, actualSimulation, Main.SIMULATION_OUTPUT +
			// suffix + "/");

			SimulateSimCase(graph, actualSimulation, MessageFormat.format(
					"{0}{1}/{2}_{3}_", Main.SIMULATION_OUTPUT, Main.SUFFIX,
					simulationCase.indexOf(actualSimulation) % SoS + 1,
					simulationCase.indexOf(actualSimulation) / SoS + 1));

			log.d(MessageFormat
					.format("{0}. simulation of {1}. simulationCase: done (~{2} msec).",
							simulationCase.indexOf(actualSimulation) % SoS + 1,
							simulationCase.indexOf(actualSimulation) / SoS + 1,
							System.currentTimeMillis() - start));

		}
		Logger.setLevel(Logger.DEBUG);
	}

	public void SimulateSimCase(
			final SimpleWeightedGraph<User, DefaultWeightedEdge> graph,
			String simCase, String output) throws IOException {
		int log = 0;
		List<User> userList = new ArrayList<User>(graph.vertexSet());
		FileWriter writer = null;
		double p = Main.PROBABILITY_OF_SPREADING;
		String edgeStatus = "";

		// Set trust for all user
		for (User u : userList) {
			u.setTrust(p
					+ Math.signum(Math.random() - 1)
					* Double.parseDouble(simCase.substring(u.getID(),
							u.getID() + 1)) * 0.1);
		}
		/*
		 * Collections.sort(userList, new Comparator<User>() {
		 * 
		 * @Override public int compare(User arg0, User arg1) { return
		 * graph.degreeOf(arg0) < graph.degreeOf(arg1) ? 1 : 0; }
		 * 
		 * });
		 */

		/**
		 * Minden szimulációban véletlenszerűen átjárhatatlanná tesz utakat...
		 * Kérdés: vajon elterjedhet-e a divat.
		 */
		// Set edge weights for all edge
		for (DefaultWeightedEdge e : graph.edgeSet()) {
			graph.setEdgeWeight(e, Math.random() > Math.max(graph
					.getEdgeSource(e).getTrust(), graph.getEdgeTarget(e)
					.getTrust()) ? INFINITE : 0);

			/*
			edgeStatus += MessageFormat.format("{0} {1} {2}\n",
					graph.getEdgeSource(e), graph.getEdgeTarget(e),
					graph.getEdgeWeight(e) == INFINITE ? 0 : 1);
			*/
			// System.err.println(edgeStatus);
		}
		
		FileWriter edgeStatusWriter = new FileWriter(output);
		edgeStatusWriter.append(edgeStatus);
		edgeStatusWriter.close();
		
		
		for (User origin : userList) {

			writer = new FileWriter(output + origin.getID());

			/*
			 * System.out.println("\n" + log + ". usertől indulva (" +
			 * graph.degreeOf(origin) + " szomszéd)");
			 */
			User x, y;

			// Módosított BFS: minden élátlépéskor Math.random()
			Queue<User> FIFO = new LinkedList<User>();

			// User origin = userList.get(i);
			FIFO.offer(origin);
			writer.append(origin.toString() + ", ");

			// System.out.print(origin.toString() + ", ");
			while (!FIFO.isEmpty()) {
				x = FIFO.remove();
				// x = origin;
				x.setBeliever(true);

				// System.out.println("---pop(): " + x.toString());
				// System.out.print(x.toString() + ", ");
				for (DefaultWeightedEdge e : graph.edgesOf(x)) {

					/****************************************/
					// boolean isTraversable = Math.random() > p ? TRUE : FALSE;
					/****************************************/
					if (graph.getEdgeWeight(e) < INFINITE) {
						y = graph.getEdgeSource(e).equals(x) ? graph
								.getEdgeTarget(e) : graph.getEdgeSource(e);

						if (!y.isBeliever()) {
							y.setBeliever(true);

							int m = 0;
							// String s = "";
							for (User u : userList) {
								m += u.isBeliever() ? 1 : 0;
								// s += u.isBeliever() ? '1' : ' ';
							}
							// System.out.println(s + ":\t" + m);

							// System.out.println(":\t" + m + ":\t" +
							// y.getID());

							// System.out.print(y.toString() + ", ");
							FIFO.offer(y);
							writer.append(y.toString() + ", ");
							if (m == 100) {
								FIFO.clear();
								// System.err.println("this sim. ended, because the queue exhausted");
							}
						}
					}
				}
			}
			// System.out.println("");
			log++;
			for (User user : userList) {
				user.reset();
			}

			try {
				writer.close();
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}

	}

	private List<String> generateSimulation(String file) {
		Logger.setLevel(Logger.NONE);

		// generation of simulationCases
		int act;

		List<Character> distribution = new ArrayList<Character>();
		List<String> simulation = new ArrayList<String>();
		try {

			FileWriter writer = new FileWriter(file);

			for (int j = 0; j < Main.SIZE_OF_SIMULATION_CASES; j++) {

				StringBuilder builder = new StringBuilder();
				distribution.clear();

				// first distribution
				for (int i = 0; i < Main.SIZE_OF_COMMUNITY; i++) {
					act = (int) (Math.random() * 1000);
					distribution.add((act <= 150) ? '1' : ((act <= 850) ? '2'
							: '3'));
					builder.append(distribution.get(i));
				}

				builder.append('\n');

				log.i(MessageFormat.format("{0}. generated distribution: {1}",
						j, distribution.toString()));

				// Az elso eloszlasbol indulva, permutalom az elemeket es
				// kiirom.
				for (int k = 0; k < Main.SIZE_OF_SIMULATIONS - 1; k++) {

					// Permutalas
					Collections.shuffle(distribution);

					// Kiiras
					for (int l = 0; l < Main.SIZE_OF_COMMUNITY; l++) {
						builder.append(distribution.get(l));
					}

					builder.append('\n');
					// simulation.add(distribution.toString());
					// writer.append(distribution.toString());
					log.i(MessageFormat.format(
							"{0}. shuffle of {1}. dist.: {2}", k, j,
							distribution.toString()));
				}

				simulation.add(builder.toString());
				writer.append(builder.toString());

			}

			Logger.setLevel(Logger.DEBUG);

			writer.close();
			return simulation;
		} catch (IOException e) {
			e.printStackTrace();
		}

		return null;

	}

	private List<String> fileReader(String file) {

		List<String> simulation = new ArrayList<String>();
		String NL = System.getProperty("line.separator");
		Scanner scanner;
		try {

			scanner = new Scanner(new FileInputStream(file));
			while (scanner.hasNextLine()) {
				simulation.add(scanner.nextLine() + NL);
			}

			scanner.close();
			return simulation;

		} catch (FileNotFoundException err) {
			log.e(err.toString());
			return null;
		}
	}

}
